package pCloud.service;

import pCloud.message.RequestFile;
import pCloud.message.ReplyMessage;

import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Random;

/**
 * Implement caching of data at the proxy server. We maintain 2 maps: 1. A
 * temporary ID->RequestFile 2. RequestFile -> ReplyMessage
 * 
 * Whenever a reply message is returned from the
 */
public class DataCaching implements CachingHandle {

	// this is used to look up the request file once the reply arrived from
	// data server
	HashMap<CachedKey, RequestFile> tempMap;
	HashMap<RequestFile, ReplyMessage> cacheMap;
	int cacheHits, cacheMisses;
	// How many objects to be cached
	int maxSize;
	Random random;

	public DataCaching(int maxSize, Random r) {
		this.tempMap = new HashMap<CachedKey, RequestFile>();
		this.cacheMap = new HashMap<RequestFile, ReplyMessage>();
		this.maxSize = maxSize;
		this.cacheHits = 0;
		this.cacheMisses = 0;
		this.random = r;
	}

	public synchronized void addToTempMap(RequestFile rf,
			InetSocketAddress serverAddress) {
		tempMap.put(new CachedKey(rf.getRequestId(), serverAddress), rf);
	}

	public synchronized void removeFromTempMap(int id,
			InetSocketAddress serverAddress) {
		tempMap.remove(new CachedKey(id, serverAddress));
	}

	public synchronized RequestFile getRequestFile(int id,
			InetSocketAddress serverAddress) {
		return this.tempMap.get(new CachedKey(id, serverAddress));
	}

	@Override
	public synchronized Object getCachedValue(Object key) {
		Object o = this.cacheMap.get(key);
		if (o == null) {
			this.cacheMisses++;
		} else
			this.cacheHits++;
		return o;
	}

	@Override
	public synchronized void addToCache(Object key, Object o) {
		// System.out.println("Adding hash value " + key.hashCode() +
		// " to cache");
		if (this.cacheMap.containsKey(key))
			return;

		if (this.cacheMap.size() < maxSize) {
			this.cacheMap.put((RequestFile) key, (ReplyMessage) o);
		} else {
			// Remove a random item off the cache

			RequestFile firstKey = (RequestFile) cacheMap.keySet().toArray()[this.random
					.nextInt(cacheMap.size())];
			this.cacheMap.remove(firstKey);
			// then add to the cache
			this.cacheMap.put((RequestFile) key, (ReplyMessage) o);
		}
	}

	/*
	 * (non-Javadoc) Simple methods for preventing out-of-date records on the
	 * cache. Particularly, we remove all cache values upon updating (removing
	 * or adding) policies.
	 * 
	 * @see pCloud.service.CachingHandle#purgeCache()
	 */
	@Override
	public synchronized void purgeCache() {
		// First, remove tempMap.
		Object[] keys = tempMap.keySet().toArray();
		for (int i = 0; i < keys.length; i++)
			tempMap.remove(keys[i]);

		keys = cacheMap.keySet().toArray();
		for (int i = 0; i < keys.length; i++) {
			cacheMap.remove(keys[i]);
		}
	}

	@Override
	public double getCacheHitRate() {
		// TODO Auto-generated method stub
		return ((double) this.cacheHits) / (this.cacheHits + this.cacheMisses);
	}

}

class CachedKey {
	int requestId;
	InetSocketAddress serverAddress;

	public CachedKey(int requestId, InetSocketAddress serverAddress) {
		this.requestId = requestId;
		this.serverAddress = serverAddress;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + requestId;
		result = prime * result
				+ ((serverAddress == null) ? 0 : serverAddress.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		CachedKey other = (CachedKey) obj;
		if (requestId != other.requestId)
			return false;
		if (serverAddress == null) {
			if (other.serverAddress != null)
				return false;
		} else if (!serverAddress.equals(other.serverAddress))
			return false;
		return true;
	}

}